<?php
/**
 * 后台 (panx,总店，分店) 管理员登录认证
 *
 * Author: Zeon
 * Date: 2017/5/11
 * Time: 11:35
 * Created by PhpStorm.
 */
namespace App\Http\Controllers\Admin;

use App\Exceptions\ApiErrCode;
use App\Exceptions\ApiException;
use App\Http\Controllers\Controller;

use App\Http\Requests\Admin\GenerateTokenRequest;
use App\Http\Requests\Admin\LoginPostRequest;
use App\Http\Requests\Admin\RefreshTokenRequest;
use App\Models\Admin\CustomerAdmin;
use App\Models\Admin\PanxAdmin;
use App\Models\Admin\StoreAdmin;
use App\Models\Customer\Customer;
use App\Models\Logger\LogType;
use App\Models\Logger\OperateLog;
use App\Models\Store\Store;
use App\Services\Factories\AdminFactory;
use App\Services\Utils\ApiChecker;
use App\Services\Utils\ApiResponser;
use App\Services\Locale\Language;
use App\Services\Utils\Guard;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use Tymon\JWTAuth\Contracts\JWTSubject;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\JWT;
use Tymon\JWTAuth\JWTGuard;


class AdminAuthController extends Controller
{
    use ApiResponser,
        ApiChecker;

    const ROUTES = [
        'login'         => 'admin.admin-auth.login',
        'refresh-token' => 'admin.admin-auth.refresh-token',
    ];

    const LOGIN_TYPE_PANX = 'panx';
    const LOGIN_TYPE_CUSTOMER = 'customer';
    const LOGIN_TYPE_STORE = 'store';

    const ALL_LOGIN_TYPES = [
        self::LOGIN_TYPE_PANX,
        self::LOGIN_TYPE_CUSTOMER,
        self::LOGIN_TYPE_STORE,
    ];

    const LOGIN_TYPE_GUARD_MAP = [
        self::LOGIN_TYPE_PANX     => Guard::ADMIN_PANX,
        self::LOGIN_TYPE_CUSTOMER => Guard::ADMIN_CUSTOMER,
        self::LOGIN_TYPE_STORE    => Guard::ADMIN_STORE,
    ];

    const LOGS_MAP = [
        self::LOGIN_TYPE_PANX     => '超管登录后台',
        self::LOGIN_TYPE_CUSTOMER => '客户登录后台',
        self::LOGIN_TYPE_STORE    => '店铺登录后台',
    ];

    const LOG_TYPE_ID_MAP = [
        self::LOGIN_TYPE_PANX     => LogType::ADMIN_PANX_LOGIN,
        self::LOGIN_TYPE_CUSTOMER => LogType::ADMIN_CUSTOMER_LOGIN,
        self::LOGIN_TYPE_STORE    => LogType::ADMIN_STORE_LOGIN,
    ];

    /**
     * 后台管理员的登录 职员的登录处理，返回token和角色id
     * @test
     *
     * @param LoginPostRequest|Request $request
     *
     * @return array|\Illuminate\Http\JsonResponse
     * @throws ApiException
     * @throws \Exception
     */
    public function login(LoginPostRequest $request)
    {
        // 先设置 app 的语言，可以正常返回报错信息
        $request_lang = Language::getLoginLanguageCode();
        app()->setLocale($request_lang);

        $adminInterface = AdminFactory::create($request->type);

        // find admin
        $admin = $adminInterface->findAdmin($request->account);

        list($ttl, $token) = $this->generateToken($request, $admin);

        // set language
        $admin->update(['language' => $request_lang]);

        // 返回管理员的角色和权限
        $roles = $admin->roles;
        $permissions = $admin->getAllPermissions();

        // 返回对应的客户类型和是否开通发型试戴字段
        $admin = $adminInterface->loginAdminInfo($admin);

        $admin->addHidden('roles', 'permissions', self::LOGIN_TYPE_CUSTOMER, self::LOGIN_TYPE_STORE);

        $this->logLogin($admin, self::LOG_TYPE_ID_MAP[$request->type],  self::LOGS_MAP[$request->type]);

        return $this->response(compact('token', 'ttl', 'admin', 'roles', 'permissions'));
    }

    /**
     * @param $token
     *
     * @throws ApiException
     */
    private function passwordVerifyCheck($token)
    {
        if (! $token) {
            throw New ApiException(ApiErrCode::INVALID_PASSWORD, trans('admin.password'));
        }
    }

    /**
     * @deprecated (前端处理)
     *
     * @return array|\Illuminate\Http\Response
     * @throws ApiException
     */
    public function logout()
    {
        try {
            $logout = Auth::guard('admin')->logout();

            return $this->response(compact('token', 'logout'));
        } catch (\Exception $e) {
            throw New ApiException(ApiErrCode::TOKEN_ERROR, 'Token error admin logout');
        }
    }

    /**
     * 创建登录 log 日志
     *
     * @param $staff
     * @param $log_type_id
     * @param $log
     */
    private function logLogin($staff, $log_type_id, $log)
    {
        OperateLog::createLog($staff, $staff, $log_type_id, $log, $staff->customer_id, $staff->store_id);
    }

    /**
     * @param LoginPostRequest $request
     * @param PanxAdmin|CustomerAdmin|StoreAdmin|Model $admin
     *
     * @return mixed
     * @throws ApiException
     * @throws \Exception
     */
    private function tryGenerateToken(LoginPostRequest $request, Model $admin)
    {
        $guard = $this->getGuardName($request->type);

        /** @var JWTGuard $jwt */
        $jwt = Auth::guard($guard);

        try {
            /**
             * 不能用 credentials ，JWT 0.5.* 不能实现 multi auth
             * attempt to verify the credentials and create a token for the user
             *
             * @link https://github.com/tymondesigns/jwt-auth/issues/513#issuecomment-186087297
             */
            $token = $jwt->attempt([
                'account'  => $request->account,
                'password' => $request->password
            ]);
                $this->passwordVerifyCheck($token);
            
            // save token
            $admin->update(['remember_token' => $token]);

        } catch (JWTException $e) {
            if (config('app.debug')) throw $e;

            throw New ApiException(ApiErrCode::TOKEN_GENERATE_FAIL, trans('admin.token'));
        }
        
        return $token;
    }


    /**
     * @param string $type
     * @return string
     * @throws \Exception
     */
    private function getGuardName(string $type): string
    {
        if (isset(self::LOGIN_TYPE_GUARD_MAP[$type])) {
            return self::LOGIN_TYPE_GUARD_MAP[$type];
        }

        $this->checkFailed('error login type');
    }

    /**
     * @param LoginPostRequest $request
     * @param CustomerAdmin|StoreAdmin|PanxAdmin|JWTSubject $admin
     *
     * @return string
     * @throws \Exception
     */
    private function generateDebugLoginToken(LoginPostRequest $request, JWTSubject $admin): string
    {
        $guard = $this->getGuardName($request->type);

        $token = Auth::guard($guard)->tokenByID($admin->id);

        return $token;
    }

    /**
     * 调试登录，可使用任意密码
     *
     * @param LoginPostRequest $request
     *
     * @return bool
     */
    private function isDebugLogin(LoginPostRequest $request): bool
    {
        return $request->debug === config('pkx.admin_login_debug_token');
    }

    /**
     * @param LoginPostRequest $request
     *
     * @return string
     */
    private function getLoginLanguage(LoginPostRequest $request): string
    {
        return $request->lang
            ?? Language::normalizeHeaderLanguage($request->header('Accept-Language'))
            ?? Language::default();
    }

    /**
     * 由上级跳转下级的一次性登录
     *
     * @param string $login_account
     * @param JWTSubject|StoreAdmin|CustomerAdmin|null|mixed $guard_user
     *
     * @return bool
     */
    private function isSuperAdminRedirectLogin(string $login_account, ?JWTSubject $guard_user): bool
    {
        return $guard_user && $guard_user->account === $login_account;
    }

    /**
     * 超管 / 商家 在后台直接登录 商家 / 店铺的后台需要生成下级对应的 token
     * @used-by isSuperAdminRedirectLogin
     *
     * @param GenerateTokenRequest $request
     *
     * @return array|\Illuminate\Http\Response
     * @throws \Exception
     */
    public function generateTokenForId(GenerateTokenRequest $request)
    {
        $guard = $this->getGuardName($request->type);
        /** @var JWTGuard $jwt */
        $jwt = Auth::guard($guard);

        $alive_minute = 1;

        $token = $jwt->setTTL($alive_minute)->tokenById($request->id);
            $this->checkItemExist($token); // id 不对，token 为 null

        return $this->response(compact('token'));
    }

    /**
     * @test
     *
     * 刷新后台管理员 token
     *
     * @param RefreshTokenRequest $request
     *
     * @return array|\Illuminate\Http\Response
     * @throws \Exception
     */
    public function refreshToken(RefreshTokenRequest $request)
    {
        /** @var JWTGuard $jwt */
        $jwt = Auth::guard($this->getGuardName($request->type));

        try {
            $token = $jwt->refresh();
        } catch (\Exception $e) {
            $this->checkFailed($e->getMessage());
        }

        return $this->response(compact('token'));
    }

    /**
     * @param LoginPostRequest $request
     * @param $admin
     * @return array
     * @throws ApiException
     * @throws \Exception
     */
    private function generateToken(LoginPostRequest $request, $admin): array
    {
        $guard = $this->getGuardName($request->type);

        /** @var JWTGuard|JWT $jwt */
        $jwt = Auth::guard($guard);

        $ttl = $jwt->factory()->getTTL();

        if ($this->isSuperAdminRedirectLogin($request->account, $jwt->user())) {
            $jwt->invalidate(); // expired once login temp token

            $token = $this->generateDebugLoginToken($request, $admin);
        } elseif ($this->isDebugLogin($request)) {
            $token = $this->generateDebugLoginToken($request, $admin);
        } else {
            $token = $this->tryGenerateToken($request, $admin);
        }
        return array($ttl, $token);
    }
}